package edu.tfh.s2.ehkbot.daten.simulation;

import java.util.Date;

import edu.tfh.s2.ehkbot.daten.Protokoll;
import edu.tfh.s2.ehkbot.daten.Zeitgeber.ZeitgeberState;

/**
 * State für Zeitgeber welches für den Zustands Simulation zuständig ist. Implementiert eine Uhr, ermöglicht
 * die Zeit schneller oder Langsammer laufen zu lassen. Insbesonder wichtig um schneller testen zu können.
 * 
 * @author s2zehn
 */
public class ZeitgeberSimulation implements ZeitgeberState, Cloneable {

	private static Long overheadGetCurrentDate = null;
	private static Long overheadSleep = null;
	private Date currentDate = new Date();

	private long startDate = System.nanoTime();
	private double zeitRaffer;

	/**
	 * @param zeitRaffer
	 *            Der Faktor um den der Zeitgeber schneller oder langsamer laufen soll. (2 bedeutet doppelt so
	 *            schnell, 0.5 halbe so schnell wie Echtzeit), zeitRaffer darf nicht Negativ sein.
	 */
	public ZeitgeberSimulation(double zeitRaffer) {
		setGeschwindigkeit(zeitRaffer);
		if (overheadGetCurrentDate == null || overheadSleep == null)
			messeOverhead();
	}

	/**
	 * @see java.lang.Object#clone()
	 */
	@Override
	public Object clone() {
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
			Protokoll.getInstanc().exception(e);
		}
		return null;
	}

	/**
	 * @see edu.tfh.s2.ehkbot.daten.Zeitgeber.ZeitgeberState#getCurrentDate()
	 */
	public Date getCurrentDate() {
		long currentTime = System.nanoTime();
		long timeLeft = currentTime - startDate - overheadGetCurrentDate;
		if (timeLeft < 0)
			timeLeft = 0;
		double newDateAsNano = startDate + timeLeft * zeitRaffer;
		double newDateAsMs = newDateAsNano / 10e5;
		return new Date(currentDate.getTime() + Math.round(newDateAsMs));
	}

	/**
	 * Misst wie lange Funktionsaufrufe brauchen um diesen Fehler bei den berechnungen zu korigieren.
	 */
	private void messeOverhead() {
		overheadGetCurrentDate = new Long(0);
		overheadSleep = new Long(0);
		long start = System.nanoTime();
		Date d = getCurrentDate();
		long vergangen = System.nanoTime() - start;
		overheadGetCurrentDate = new Long(vergangen);
		start = System.nanoTime();
		sleep(0);
		vergangen = System.nanoTime() - start;
		vergangen = vergangen + d.getTime() - d.getTime(); // tut nichts, soll verhindern das d wegoptimiert
		// wird
		overheadSleep = new Long(vergangen);
	}

	/**
	 * Erlaubt das Setzen der Geschwindigkeit.
	 * 
	 * @param zeitRaffer
	 *            der Zeitraffer {@link ZeitgeberSimulation#ZeitgeberSimulation(double)}
	 */
	public void setGeschwindigkeit(double zeitRaffer) {
		if (zeitRaffer < 0)
			throw new IllegalArgumentException("The Argument zeitRaffer is Negative");
		this.zeitRaffer = zeitRaffer;
	}

	/**
	 * @see edu.tfh.s2.ehkbot.daten.Zeitgeber.ZeitgeberState#sleep(long)
	 */
	@Override
	public void sleep(long zeitInMs) {
		/*
		 * Thread.sleep würde Rechenleistung sparen, ist aber je nach System zu ungebau
		 */
		if (zeitInMs < 0)
			throw new IllegalArgumentException("The Argument zeitInMs is Negative");
		double end = System.nanoTime() + (zeitInMs * 10e5) / zeitRaffer - overheadSleep;
		while (end > System.nanoTime()) {
		}
	}
}
